/**
 * SlotDuino
 * 
 * Copyright (c) 2011/2012 Thiago Hummel Moreira, Ricardo Mizuuti, Rafael Mufato, Bruno Sakai, Andre Turina
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.Threading;
using Microsoft.SPOT;

namespace SlotDaemon
{
    public enum RaceState
    {
        New = 0,
        Running = 1,
        Paused = 2,
        Finished = 3
    }

    public class Race
    {
        #region Private attributes
        private int _id;
        private TimeSpan _duration;
        private byte[] _lanesIds;
        private RaceState _state;
        private Timer _timer;
        private TimeSpan _timeLeft;
        private DateTime _startedAt;
        private DateTime _finishedAt;
        private DateTime _pausedAt;
        private DateTime _resumedAt;
        #endregion

        #region Public properties
        public int Id { get { return _id; } }
        public RaceState State { get { return _state; } }
        public byte[] LanesIds { get { return _lanesIds; } }
        public DateTime StartedAt { get { return _startedAt; } }
        public DateTime FinishedAt { get { return _finishedAt; } }
        public TimeSpan Duration { get { return _duration; } }
        #endregion

        #region Public events
        /// <summary>
        /// Fired when the state property changes (start, pause, resume and end methods)
        /// </summary>
        public event RaceStateChangedEventHandler OnRaceStateChanged;
        /// <summary>
        /// Fired when the timer reach the end or the race is aborted (via external command)
        /// </summary>
        public event RaceEndedEventHandler OnRaceEnded;
        #endregion

        #region Constructors
        public Race(int id, TimeSpan duration, byte[] lanes)
        {
            _id = id;
            _duration = duration;
            _lanesIds = lanes;

            SetState(RaceState.New);
        }
        #endregion

        private void TimerCreate(TimeSpan duration)
        {
            _timer = new Timer(TimerEnd, null, _duration, TimeSpan.Zero);
        }

        private void TimerAbort()
        {
            _timer.Dispose();
            _timer = null;
        }

        private void TimerEnd(object o)
        {
            //Ensure _timer will be disposed
            using (_timer)
            {
                End();
            }
        }

        private void SetState(RaceState state)
        {
            RaceState oldState = _state;
            _state = state;

            //Fire the OnStateChanged Event
            RaceStateChangedEventHandler handler = OnRaceStateChanged;
            if (null != handler)
                handler(this, new RaceStateChangedEventArgs(DateTime.Now, state, oldState));
        }

        public bool Contains(byte laneId)
        {
            bool contain = false;
            for (byte i = 0; i < _lanesIds.Length; i++)
            {
                if (laneId == _lanesIds[i])
                {
                    contain = true;
                    break;
                }
            }
            
            return contain;
        }
        
        public bool Start()
        {
            if (State != RaceState.New)
                return false;
            
            _timeLeft = _duration;
            TimerCreate(_duration);

            _startedAt = _resumedAt = DateTime.Now;
            
            SetState(RaceState.Running);
            return true;
        }

        public bool Pause()
        {
            if (State != RaceState.Running)
                return false;

            _pausedAt = DateTime.Now;
            _timeLeft -= _pausedAt - _resumedAt;
            TimerAbort();

            SetState(RaceState.Paused);
            return true;
        }

        public bool Resume()
        {
            if (State != RaceState.Paused)
                return false;

            _resumedAt = DateTime.Now;
            TimerCreate(_timeLeft);

            SetState(RaceState.Running);
            return true;
        }

        public bool End(bool aborted = false)
        {
            if (State == RaceState.Finished)
                return false;

            _finishedAt = DateTime.Now;
            SetState(RaceState.Finished);

            //Fire the OnRaceEnded Event
            RaceEndedEventHandler handler = OnRaceEnded;
            if (null != handler)
                handler(this, new RaceEndedEventArgs(_finishedAt, aborted));

            return true;
        }

        public override string ToString()
        {
            string ret = "Start: " + StartedAt.ToString() + " - End: " + FinishedAt.ToString();
            return ret;
        }
    }

    #region Event support classes
    public class RaceEndedEventArgs : EventArgs
    {
        public DateTime When { get; internal set; }
        public bool Aborted { get; internal set; }

        public RaceEndedEventArgs(DateTime when, bool aborted)
        {
            When = when;
            Aborted = aborted;
        }
    }

    public delegate void RaceEndedEventHandler(Race sender, RaceEndedEventArgs e);

    public class RaceStateChangedEventArgs : EventArgs
    {
        public DateTime When { get; internal set; }
        public RaceState State { get; internal set; }
        public RaceState OldState { get; internal set; }

        public RaceStateChangedEventArgs(DateTime when, RaceState state, RaceState oldState)
        {
            When = when;
            State = state;
            OldState = oldState;
        }
    }
    public delegate void RaceStateChangedEventHandler(Race sender, RaceStateChangedEventArgs e);
    #endregion
}
